new function(_)
{
	var runner = new base2.Package(this, {
		name: "runner",
		parent: parchment,
		imports: "fatal, parchment.beret, parchment.gnusto",
		exports: "EngineRunner"
	});

	eval(this.imports);

	function EngineRunner(engine, zui, logfunc) {
	  this._engine = engine;
	  this._zui = zui;
	  this._isRunning = false;
	  this._isInLoop = false;
	  this._isWaitingForCallback = false;
	  this._log = logfunc;

	  var self = this;

	  var methods = {
	    stop: function() {
	      self._isRunning = false;
	    },

	    run: function() {
	      var size = self._zui.getSize();
	      self._zui.setVersion(self._engine.m_version);

	      self._isRunning = true;
	      self._engine.m_memory[0x20] = size[1];
	      self._engine.m_memory[0x21] = size[0];
	      self._continueRunning();
	    },

	    _continueRunning: function() {
	      while (self._isRunning && !self._isWaitingForCallback) {
	        self._loop();
	      }
	    },

	    _receiveLineInput: function(input) {
	      self._isWaitingForCallback = false;

	      // For now we'll say that a carriage return is the
	      // terminating character, because we don't actually support
	      // other terminating characters.
	      self._engine.answer(0, 13);

	      self._engine.answer(1, input);
	      if (!self._isInLoop) {
	        self._continueRunning();
	      } else {
	        /* We're still inside _loop(), so just return. */
	      }
	    },

	    _receiveCharacterInput: function(input) {
	      self._isWaitingForCallback = false;
	      self._engine.answer(0, input);
	      if (!self._isInLoop) {
	        self._continueRunning();
	      } else {
	        /* We're still inside _loop(), so just return. */
	      }
	    },

	    _unWimpOut: function() {
	      self._isWaitingForCallback = false;
	      if (!self._isInLoop) {
	        self._continueRunning();
	      } else {
	        /* We're still inside _loop(), so just return. */
	      }
	    },

	    _loop: function() {
	      if (self._isInLoop)
	        throw new FatalError("Already in loop!");

	      self._isInLoop = true;
	      var engine = self._engine;

	      engine.run();

	      var text = engine.consoleText();
	      if (text)
	        self._zui.onPrint(text);

	      var effect = '"' + engine.effect(0) + '"';

	      var logString = "[ " + engine.effect(0);

	      for (var i = 1; engine.effect(i) != undefined; i++) {
	        var value = engine.effect(i);
	        if (typeof value == "string")
	          value = value.quote();
	        logString += ", " + value;
	      }

	      self._log(logString + " ]");

	      switch (effect) {
	      case GNUSTO_EFFECT_INPUT:
	        self._isWaitingForCallback = true;
	        self._zui.onLineInput(self._receiveLineInput);
	        break;
	      case GNUSTO_EFFECT_INPUT_CHAR:
	        self._isWaitingForCallback = true;
	        self._zui.onCharacterInput(self._receiveCharacterInput);
	        break;
	      case GNUSTO_EFFECT_SAVE:
	        engine.saveGame();
	        if (self._zui.onSave(engine.saveGameData()))
	          engine.answer(0, 1);
	        else
	          engine.answer(0, 0);
	        break;
	      case GNUSTO_EFFECT_RESTORE:
	        var restoreSuccess = self._zui.onRestore();
	        if (!restoreSuccess)
	          engine.answer(0, 0);
	        break;
	      case GNUSTO_EFFECT_QUIT:
	        self.stop();
	        self._zui.onQuit();
	        break;
	      case GNUSTO_EFFECT_RESTART:
	        self.stop();
	        self._zui.onRestart();
	        break;
	      case GNUSTO_EFFECT_WIMP_OUT:
	        self._isWaitingForCallback = true;
	        self._zui.onWimpOut(self._unWimpOut);
	        break;
	      case GNUSTO_EFFECT_BREAKPOINT:
	        throw new FatalError("Unimplemented effect: " + effect);
	      case GNUSTO_EFFECT_FLAGS_CHANGED:
	        var isToTranscript = engine.m_printing_header_bits & 0x1;
	        var isFixedWidth = engine.m_printing_header_bits & 0x2;
	        self._zui.onFlagsChanged(isToTranscript, isFixedWidth);
	        break;
	      case GNUSTO_EFFECT_PIRACY:
	        throw new FatalError("Unimplemented effect: " + effect);
	      case GNUSTO_EFFECT_STYLE:
	        self._zui.onSetStyle(engine.effect(1),
	                             engine.effect(2),
	                             engine.effect(3));
	        break;
	      case GNUSTO_EFFECT_SOUND:
	        // TODO: Actually implement this; for now we'll just
	        // ignore it since it's not a required element of 'terps
	        // and we don't want the game to crash.
	        break;
	      case GNUSTO_EFFECT_SPLITWINDOW:
	        self._zui.onSplitWindow(engine.effect(1));
	        break;
	      case GNUSTO_EFFECT_SETWINDOW:
	        self._zui.onSetWindow(engine.effect(1));
	        break;
	      case GNUSTO_EFFECT_ERASEWINDOW:
	        self._zui.onEraseWindow(engine.effect(1));
	        break;
	      case GNUSTO_EFFECT_ERASELINE:
	        throw new FatalError("Unimplemented effect: " + effect);
	      case GNUSTO_EFFECT_SETCURSOR:
	        self._zui.onSetCursor(engine.effect(2),
	                              engine.effect(1));
	        break;
	      case GNUSTO_EFFECT_SETBUFFERMODE:
	        self._zui.onSetBufferMode(engine.effect(1));
	        break;
	      case GNUSTO_EFFECT_SETINPUTSTREAM:
	      case GNUSTO_EFFECT_GETCURSOR:
	        throw new FatalError("Unimplemented effect: " + effect);
	        break;
	      case GNUSTO_EFFECT_PRINTTABLE:
	        var numLines = engine.effect(1);
	        // TODO: There's probably a more concise way of doing this
	        // by using some built-in array function.
	        var lines = [];
	        for (i = 0; i < numLines; i++)
	          lines.push(engine.effect(2+i));
	        self._zui.onPrintTable(lines);
	        break;
	      }

	      self._isInLoop = false;
	    }
	  };
	  for (name in methods)
	    self[name] = methods[name];
	}

	eval(this.exports);
};
